package slave;

/**
 * Classe responsabile dell'attivazione dei SessionStreamManager, dei meccanismi di controllo
 * codifica, di aggiornamento del lamda e di usurpo.
 * @author alex 
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Vector;
import org.jdom.*; 
import org.jdom.input.SAXBuilder;
import commonUtility.LineUtility;
import commonUtility.XmlHandler;
import commonUtility.XmlTransferHandler;
import slave.SlaveComunicator;
import slave.SlaveComunicatorListener;
import slave.SlaveComunicator.DeleteReadyEvent;
import slave.SlaveComunicator.InquiryReadyEvent;
import slave.SlaveComunicator.JobReadyEvent;
import slave.sspGUI.MonitorObservervable;
import slave.sspGUI.StreamProviderMonitor;
import java.util.EventObject;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.swing.event.EventListenerList;

public class SlaveStreamProvider extends Thread implements SlaveComunicatorListener{

	public StreamProviderMonitor systemMonitor;
	public Hashtable <String,Thread>activedThreadTransmitterTable = new Hashtable<String,Thread>();
	public EventListenerList listenerList;
	public XmlHandler xmlSynchronized;
	public XmlTransferHandler xmlTransfer_0;
	public XmlTransferHandler xmlTransfer_1;
	public int currentQuality = 0;
	public SlaveComunicator sc = null;	
	public LambdaComputationThread lct;
	public LambdaComputationEvent evt;
	private Object lambdaValuesSemaphore = new Object();
	private SlaveDeleteServer_MasterConnectorThread_after slaveDeleteServerMasterConnectorThread_after = null;
	private SlaveDeleteServer_MasterConnectorThread_before slaveDeleteServerMasterConnectorThread_before = null;
	private SlaveStreamProviderDeleteClientsThread slaveStreamProviderDeleteClientsThread= null;
	private MonitorObservervable obs;
	private Vector <String> message = new Vector<String>();
	private String fileJobPath = "./slaveTmp/job.xml";

	public	SlaveStreamProvider(XmlHandler xmlSynchronized){		
		listenerList = new EventListenerList(); 	
		this.xmlSynchronized = xmlSynchronized;
		xmlTransfer_0 = new XmlTransferHandler();
		xmlTransfer_1 = new XmlTransferHandler();
		evt = new LambdaComputationEvent(this);   
		BluetoothClientPortContainer.getInstance().removeAll();
	}
	public SlaveStreamProvider getMe() {
		return this;
	}

	public void run(){
		System.err.println("[SlaveStreamProvider] "+Thread.currentThread().getName()+" Avviato...");
		slaveStreamProviderDeleteClientsThread = new SlaveStreamProviderDeleteClientsThread();
		slaveStreamProviderDeleteClientsThread.setName("slaveStreamProviderDeleteClientsThread");
		slaveStreamProviderDeleteClientsThread.start();	
		systemMonitor = new StreamProviderMonitor(this);
		obs = new MonitorObservervable(systemMonitor);
		boolean active = true;

		while(active){
			synchronized (this) {
				try {
					System.out.println("SlaveStreamProvider: Mi blocco in attesa di InquiryReady ");
					if(!message.isEmpty()){
						message.clear();
					}
					message.add(0,"EVT");
					message.add(1,"SSP: Wainting new Inquiry..");
					obs.notify(message);
					this.wait();//Mi blocco in attesa di InquiryReady 
					System.out.println("SlaveStreamProvider: SBLOCCATO dalla notify di inquiryReady");
					synchronized(getLambdaValuesSemaphore()) {
						lct = new LambdaComputationThread(xmlSynchronized,currentQuality);  
						lct.start();
						try {
							lct.join();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					System.out.println("[EVT] [SlaveStreamProvider] Lancio il fireLambdaComputation..");	
					fireLambdaComputationDone(evt);
					this.wait();// Mi blocco in attesa del file job.xml 
					Timestamp timeStamp = new Timestamp(System.currentTimeMillis());
					System.err.println("[SlaveStreamProvider] Received new file job.xml: "+timeStamp);		
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("[EVT] [SlaveStreamProvider] SlaveStreamProvider Unlock.");
				if(!message.isEmpty()){
					message.clear();
				}
				message.add(0,"EVT");
				message.add(1,"SSP: Unlock after Lambda Computation Done.");
				obs.notify(message);
				clientsActivation(); // Lancio 			
			}	
		}		
	}

	/****************************************************************
	 * PARTE RELATIVA ATTIVAZIONE DEI CLIENT IN STATO ACTIVABLE
	 ****************************************************************/
	/**
	 * Questo metodo leggerà da job.xml, una lista di client da attivare con i relativi tag:
	 * mac
	 * file
	 * time
	 * port
	 */

	@SuppressWarnings("unchecked")
	public void clientsActivation() {
		System.out.println("DEBUG [SlaveStreamProvider] : Entro in CLIENTACTIVATION");
		System.out.println("DEBUG [SlaveStreamProvider] : Numero di porte utilizzate: "+BluetoothClientPortContainer.getInstance().getNumber());
		try{
			SessionStreamManager threadTransmitter = null;
			String mac= null;
			String file=null;
			String time = null;
			String port=null;
			SAXBuilder builder = new SAXBuilder(); 
			Document document = null;
			Vector <String> activable = xmlSynchronized.changeStatusClientXml("activableList",null);	

			try {
				document = builder.build(new File(fileJobPath));//Recupero i client passati dal master dopo il protocollo loadbalancing
			} catch (JDOMException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} 
			Element root = document.getRootElement(); 
			List<Element> children = root.getChildren(); 
			Iterator<Element> iterator = children.iterator(); 
			int number = children.size();			
			//Notify to GUI
			if(!message.isEmpty()){
				message.clear();
			}
			message.add(0,"EVT");
			message.add(1,"SSP: Number of client to activate: "+number);
			obs.notify(message);
			int totalClient = activedThreadTransmitterTable.size()+number;//Valuto quanti client dovrò attivare, considerando anche quelli già attivi.
			while(iterator.hasNext()){ 
				System.out.println("[SlaveStreamProvider] Recupero Client da JOB");
				Element item = (Element)iterator.next(); 
				mac = item.getAttributeValue("MAC");
				file = item.getChildText("SOURCE");
				time = item.getChildText("TRANSMITTEDTIME");
				port = item.getChildText("PORT");
				//Aggiunto controllo dopo simulazioni per PENDING and TOIGNORE
				System.err.println("[SlaveStreamProvider] MAC IN JOB"+mac);
				if(!activable.contains(mac) && !activable.isEmpty()){// Se il JOB è uno degli attivabili continuo, altrimenti lo cancello dal file.
					Vector <String> client = new Vector();
					client.add(0,mac);
					xmlSynchronized.changeStatusClientXml("delete",client);		
					if(!message.isEmpty()){
						message.clear();
					}
					message.add(0,"EVT");
					message.add(1,"SSP: TOIGNORE: "+mac);
					obs.notify(message);
				}			
				if(!activedThreadTransmitterTable.containsKey(mac)){
					System.out.println("[SlaveStreamProvider] Client non presente");
					if(activedThreadTransmitterTable.size()==7){//Controllo per il cambio di local device bt(Limite imposto dallo standar BT)
						threadTransmitter =  new SessionStreamManager(systemMonitor,mac,file,time,port,getQuality(totalClient),this,"helper");
					} else threadTransmitter =  new SessionStreamManager(systemMonitor,mac,file,time,port,getQuality(totalClient),this,"main");
					System.out.println("Attivo Thread SessionStreamManager verso: "+mac);
					threadTransmitter.start();//Attivo la richiesta di connessione.
					activedThreadTransmitterTable.put(mac,threadTransmitter);//Inserisco il thread per poi poterne controllare lo stato.
					Vector <String> info = new Vector();
					info.add(0,mac);
					info.add(1,file);
					info.add(2,null);
					info.add(3,null);
					info.add(4,null);
					info.add(5,"PENDING");
					info.add(6,null);
					xmlSynchronized.changeStatusClientXml("update",info);
					if(!info.isEmpty()){
						info.clear();
					}
				}
			}
			activable = xmlSynchronized.changeStatusClientXml("activableList",null);	
			if(!activable.isEmpty()){
				for(int i =0; i< activable.size(); i++)
				{
					Vector <String> client = new Vector();
					client.add(0,mac);
					xmlSynchronized.changeStatusClientXml("delete",client);	
				}	
			}	
		}catch(Exception e){
			e.printStackTrace();
		}
	}	
	/**
	 *Questo metodo sarà utilizzato dal Thread di controllo 
	 *SlaveStreamProviderClientsActivityController, per evitare possibili corse critiche;
	 *Es. Un client che rimane con lo stato "ACTIVE", non riuscendo causa eventuali eccezioni
	 *a cancellarsi dal file gateway.xml dovrà essere cancellato per evitare che non possa 
	 *più usufruire del servizio.
	 * @param client: mac del client che si vorrà controllare.
	 */	
	public void checkClientActivity(String client) {
		SessionStreamManager ssm;
		ssm = (SessionStreamManager) activedThreadTransmitterTable.get(client);
		if(!ssm.getTrasmissionState()){// Se dalla verifica risulta non attivo aggiorno tabella e file xml.
			activedThreadTransmitterTable.remove(client);
			Vector<String> info = new Vector <String>();
			info.add(client);
			xmlSynchronized.changeStatusClientXml("remove",info);
		}
	}
	/**
	 *Ad ogni richiesta di attivazione dello stream verso un nuovo client,
	 *viene controllato il numero di client che si stanno già servendo, ed eventulamente
	 *limitata la banda nel caso sia superiore a 5, per garantire la QoS.
	 */	
	public int getQuality(int serveClients){
		if(serveClients>5){
			if(currentQuality!=4){
				ChangeCodingEvent evt = new ChangeCodingEvent(this,4);
				fireChangeCodingEvent(evt);
				currentQuality=4;
			}
			return currentQuality;
		}
		else {
			currentQuality = 0;	
		}
		return currentQuality;		
	}
	/**
	 * In ingresso sar� dato il mac del client Bluetooh da chiudere, e ritorner� un vettore con come
	 * primo elemento un intero che indica il Tempo Trasmesso
	 * secondo elemento una stringa che indica la porta da utilizzare per la trasmissione.
	 */	
	@SuppressWarnings("unchecked")
	public Vector <String> closeClient(String client,String reason) {
		SessionStreamManager ssm;
		Vector<String> info = new Vector <String>();
		info.add(client);
		Vector v = null;
		ssm = (SessionStreamManager) activedThreadTransmitterTable.get(client);
		v = ssm.removeClient(reason);
		activedThreadTransmitterTable.remove(client);
		return v;
	}	
	/**
	 * Metodo utilizzato per la rimozione del SessionStreamManager dalla
	 * tabella dei flussi attivi
	 * @param bt: mac
	 */

	public void removeFromActiveTable(String bt){
		activedThreadTransmitterTable.remove(bt);	
	}

	/**
	 * Metodo utilizzato per gestire la richiesta di usurpo client.
	 * Legge da file i client da rimuovere, e ne scrive i dati sul file xmlTransfer.xml.
	 */

	public synchronized void deleteClient(int id){
		boolean moreLines = true;	
		BufferedReader in = null;
		Vector <String> v = null;
		try {
			in = new BufferedReader(new FileReader("./slaveTmp/"+id+"_delete.txt"));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		if (id==0) xmlTransfer_0.makeXml(id);
		else xmlTransfer_1.makeXml(id);
		while (moreLines) {
			String client = LineUtility.getNextLine(in);

			if (client.equalsIgnoreCase("Nessuna linea disponibile"))
				moreLines = false;
			else {
				v = closeClient(client,"FAILOVER");
				String time = v.elementAt(0);
				String port = v.elementAt(1);
				String path = v.elementAt(2);
				/*
				 * Scrivo i client cancellati nel file transfer
				 */
				if(id==0) xmlTransfer_0.addClient(id,client, path,time,port);		
				else xmlTransfer_1.addClient(id,client, path,time,port);
			}
		}//fine while
	}

	/**
	 * @return the lambdaValuesSemaphore
	 */
	public Object getLambdaValuesSemaphore() {
		return lambdaValuesSemaphore;
	}
	/**************************************************************************************
	 * METODO UTILIZZATO PER PRENDERE IL RIFERIMENTO A SLAVE COMMUNICATOR ED EVENTI.
	 **************************************************************************************/  

	public void getSc(SlaveComunicator scom){
		this.sc = scom;	
		sc.addSlaveComunicatorListener(this);
	}
	@Override
	public void inquiryReady(InquiryReadyEvent ire) {
		System.out.println("SlaveStreamProvider: Ricevuto inquiryReady. Devo lanciare il fileLambdaComputation..");
		synchronized (sc.sm) {
			sc.sm.notify();	
		}
	}
	@Override
	public void jobReady(JobReadyEvent evt) {
		System.out.println("JobReadyEvent ricevuto");
		synchronized (evt.getSlaveManager()) {
			evt.getSlaveManager().notify();	
		}
	}
	@Override
	//Rimuovo client ed aggiorno file transfer_job.xml
	public void deleteReady(DeleteReadyEvent dre) {
		System.err.println("DEBUG: SlaveStreamProvider: ricevuto evento deleteReadyEvent");
		int threadId = dre.getFileId();
		if(!message.isEmpty()){
			message.clear();
		}
		message.add(0,"EVT");
		message.add(1,"SSP: Received SWITCH request from neighbors:  "+threadId);
		obs.notify(message);
		System.err.println("ID del Thread che invoca la deleteReady: "+threadId);
		TransferJobXmlReadyEvent evt = new TransferJobXmlReadyEvent(this);	
		fireTransferJobXmlReady(evt,threadId,dre.getSlaveManager());// Avverto lo SlaveCommunicator		
	}
	public void fireChangeCoding(int codec){
		ChangeCodingEvent evt = new ChangeCodingEvent(this,codec);
		this.fireChangeCodingEvent(evt);	
	}

	/****************************************************************
	 * METODI PER LA REALIZZAZIONE DI EVENTI LEGATI A SLAVESTREAMPROVIDER
	 ****************************************************************/

	/**
	 * Evento per il cambio di codifica!!
	 */   
	@SuppressWarnings("serial")
	public class  ChangeCodingEvent extends EventObject {
		int coding;
		public  ChangeCodingEvent(SlaveStreamProvider source,int coding) {
			super(source);		
			this.coding=coding;	
		}
		public int getCoding(){    	
			return coding;      	
		}
	}

	void fireChangeCodingEvent(ChangeCodingEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		for (int i=0; i<listeners.length; i+=2) {
			if (listeners[i]==SlaveStreamProviderListener.class) {
				((SlaveStreamProviderListener)listeners[i+1]).changeCoding(evt);
			}
		}     
	}
	/**
	 * @author alex
	 * Evento ack di LAMBDA Update. 
	 */   
	@SuppressWarnings("serial")
	public class  LambdaComputationEvent extends EventObject {

		public  LambdaComputationEvent(SlaveStreamProvider source) {
			super(source);		
		}		
	}
	void fireLambdaComputationDone(LambdaComputationEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		for (int i=0; i<listeners.length; i+=2) {
			if (listeners[i]==SlaveStreamProviderListener.class) {
				((SlaveStreamProviderListener)listeners[i+1]).lambdaComputationDone(evt);
			}
		}
	}
	/**
	 * @author alex
	 * Evento ack di LAMBDA Update. 
	 */   
	@SuppressWarnings("serial")
	public class  TransferJobXmlReadyEvent extends EventObject {
		public  TransferJobXmlReadyEvent(SlaveStreamProvider source) {
			super(source);		
		}		
	}

	void fireTransferJobXmlReady(TransferJobXmlReadyEvent evt, int threadId,SlaveStreamProvider slaveManager) {
		Object[] listeners = listenerList.getListenerList();
		for (int i=0; i<listeners.length; i+=2) {
			if (listeners[i]==SlaveStreamProviderListener.class) {
				((SlaveStreamProviderListener)listeners[i+1]).transferJobXmlReady(evt,threadId,slaveManager);
			}
		}
	}	
	public void addSlaveManagerListener(SlaveStreamProviderListener listener) {
		listenerList.add(SlaveStreamProviderListener.class, listener);
	}

	public void removeSlaveManagerListener(SlaveStreamProviderListener listener) {
		listenerList.remove(SlaveStreamProviderListener.class, listener);
	}	
	/**
	 * Metodi per la gestione del Thread DeleteClients
	 * @return
	 */

	public SlaveStreamProviderDeleteClientsThread getSlaveManagerDeleteClientsThread() {
		return slaveStreamProviderDeleteClientsThread;
	}

	public void setSlaveManagerDeleteClientsThread(
			SlaveStreamProviderDeleteClientsThread slaveManagerDeleteClientsThread) {
		this.slaveStreamProviderDeleteClientsThread = slaveManagerDeleteClientsThread;
	}


	public synchronized void setSlaveDeleteServerMasterConnectorThread(
			Thread masterConnectorThread,int id) {

		if (id==1) this.slaveDeleteServerMasterConnectorThread_after = (SlaveDeleteServer_MasterConnectorThread_after) masterConnectorThread;
		if (id==0) this.slaveDeleteServerMasterConnectorThread_before = (SlaveDeleteServer_MasterConnectorThread_before) masterConnectorThread;

		synchronized (slaveStreamProviderDeleteClientsThread) {
			slaveStreamProviderDeleteClientsThread.setId(id);
			System.err.println(Thread.currentThread().getName()+" faccio la notify su "+slaveStreamProviderDeleteClientsThread.getName());
			slaveStreamProviderDeleteClientsThread.notify();
		}

	}
	/****************************************************************
	 * INNER CLASS  UTILIZZATA PER LA CANCELLAZIONE DEI CLIENT CHE SARANNO
	 * PASSATI AI VICINI, CON I RELATIVI PARAMETRI PER L' INIZIO 
	 * DELLA TRASMISSIONE.
	 ****************************************************************/	

	class SlaveStreamProviderDeleteClientsThread extends Thread{
		private int id = 0;	
		private DeleteClientsThreadWorker[] worker = new DeleteClientsThreadWorker[2];	
		public void setId(int id) {
			this.id = id;
		}
		public void run(){
			System.err.println("[SlaveStreamProvider] Lanciato SlaveStreamProviderDeleteClientsThread!!");	
			while(true){
				synchronized (this) {
					try {
						this.wait();
						System.err.println("[SlaveStreamProvider]"+Thread.currentThread().getName()+" sbloccato dalla wait...");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					worker[id] = new DeleteClientsThreadWorker(id);
					worker[id].setName("WorkerID :"+id);
					worker[id].start();
				}	
			}		
		}//fine run			
	}// fine SlaveStreamProviderDeleteClientsThread

	class DeleteClientsThreadWorker extends Thread{
		private int id = 0;	
		private SlaveDeleteServer_MasterConnectorThread_after thread_after = null;
		private SlaveDeleteServer_MasterConnectorThread_before thread_before = null;

		public DeleteClientsThreadWorker(int id) {
			this.id = id;	
		}

		public void run(){
			//Eseguo la parte di cancellazione...	
			System.err.println("\n[SlaveStreamProvider] Partito Thread worker "+Thread.currentThread().getName()+"\n");	
			synchronized (getMe()) {
				deleteClient(id);	
			}
			/**
			 * Controllo l'id.
			 * 0 = chiamata dal BEFORE
			 * 1 = chiamata dall' AFTER			
			 */
			if (id==1) {
				thread_after = slaveDeleteServerMasterConnectorThread_after;
				synchronized(thread_after){
					System.err.println("[SlaveStreamProvider] notify su "+thread_after.getName()+" attualmente in stato "+thread_after.getState());
					thread_after.notify();
				}
			} else {
				thread_before = slaveDeleteServerMasterConnectorThread_before;
				synchronized(thread_before){
					System.err.println("[SlaveStreamProvider] notify su "+thread_before.getName()+" attualmente in stato "+thread_before.getState());
					thread_before.notify();
				}
			}

		}//fine run
	}//fine Worker
}//fine SlaveStreamProvider